我們學習 Vue 的時候,應該常常都會聽到 TypeScript,這個章節它是非必要,但我覺得也是挺重要的一環,那就讓我娓娓道來吧!
為了更快速理解,也減少字數篇幅,以下將 TypeScript 簡稱為 TS、JavaScript 簡稱為 JS。
TypeScript 是一個由微軟(MSFT)所領導開發的「強型別」(待會會介紹這是什麼)語言,他可以幫助 JavaScript 開發者更明瞭及有系統性地開發。TypeScript 在開發時期就會檢查到錯誤了,因此受到許多偏好規範性開發者的喜愛。TypeScript 會將 TS 檔案轉譯回 JS,使得瀏覽器能夠讀得懂,因此讀者可以將 TS 視為是建構在以 JS 之上的,用來彌補 JS 在型別檢查上的不足(JS會採用弱型別也是有原因的,本文將不贅述,有興趣的可以自行 Google「why javascript use weak-type」)。
因此讀者可以將 TS 視為:是為 JS 的強型別而生,提供靜態型別檢查。
OK,這裡就不再過多介紹,我們就直接進入正題吧!
其實 TS 的語法幾乎繼承自 JS,且在 JS 的基礎上再增加強型別語言該有的「保留字(keyword:是為一個語言的基本語法單字,有不可取代性,以下會做介紹)」。
保留字有非常多,包括但不限於「let、const、for、if、...」,這些語言本身所自帶的或保留的被稱作「保留字」。
我們這裡就稍微示範一下 TS 的語法結構:
let value1: number = 100; // 將 100 賦值於 value1,其型別為「number(數字)」。
let value2: number = 200; // 將 200 賦值於 value2,其型別為「number(數字)」。
console.log(value1 + value2); // 將 value1 與 value2 相加,並顯示在終端機上,可以理解為 print。
那 JS 則是:
let value1 = 100; // 沒有強加型別
let value2 = 200; // 沒有強加型別
console.log(value1 + value2);
因此如果您 JS 這樣寫,也是能夠執行:
let value1 = 100; // 沒有型別
let value2 = "hello"; // 沒有型別
console.log(value1 + value2); // 100hello
但是 TS 就不行:
let value1: number = 100; // 數字型別
let value2: string = "hello"; // 字串型別
console.log(value1 + value2); // 無法相加,會報出錯誤:型別不相容
因此開發者如果亂傳一些值,JS會全盤接受,除非開發者自己手動判斷型別,否則會造成很多開發上的問題。因此透過 TS 一開始就設定好型別,不僅能夠讓開發者快速傳入,也降低錯誤的發生機率(因為一開始就已經檢查完了)。
陣列是一個很特別的東西,陣列可以把多個值放在同一個序列中。讀者可以將此想像成你在吃糖葫蘆,你一顆一顆買,那為何不買一整串?
定義陣列也很簡單:
let nums: number[] = [100, 200, 300];
console.log(nums[0]); // 第 0 個元素,即 100。
console.log(nums[1]); // 第 1 個元素,即 200。
console.log(nums[2]); // 第 2 個元素,即 300。
要注意的是,陣列都是從第 0 個元素開始算,長度為 n,則最後一個索引(index, idx)可以寫成「第 n - 1 個元素」。以上述為例,有三個元素,第一個元素為第零個索引,最後一個元素為第二個索引。
我們可以看到,如果要將一個變數規定型別,則遵守著這個規則「變數: 型別 = 值」,函數也同樣如此(以 箭頭函式(arrow func)為例):
const add = (x: number, y: number): number => { // 定義一個箭頭函數 add(x, y),可傳入 x(型別為number)、y(型別為number)。
return x + y; // 回傳 x + y,型別為 number。
}
console.log(add(100, 200)); // 將 100 及 200 傳入 add(x, y),輸出為 300。
函數則是遵守這個規則「函數名(傳入參數1: 型別1, 傳入參數2: 型別2, ..., 傳入參數n: 型別n): 傳出型別」。
傳統函式與箭頭函式大致相同:
function add(x: number, y: number): number { // 定義一個函數 add(x, y),可傳入 x(型別為number)、y(型別為number)。
return x + y; // 回傳 x + y,型別為 number。
}
console.log(add(100, 200)); // 將 100 及 200 傳入 add(x, y),輸出為 300。
上述已經舉例了 number,那我們來舉一反三,既然有數字,那會不會有字串呢?(廢話XD),那我們一起來看吧!
我們舉一些常用的就好,其他讀者可以再自行上 TypeScript 網站 - Everyday Types上學習。
型別 | 簡介 |
---|---|
number | 數字 |
string | 字串 |
boolean | 布林 |
null | 空值 |
undefined | 未定義值、未賦值 |
any | 任何 |
前三個,相信讀者應該都能夠理解,number = 數字, string = 字串, boolean = 布林(真、假)。
比較特別需要說明的是後三個常用的:
其中 null 與 undefined 我知道讀者剛開始學習的時候常常會搞混,null 比較像是已經建立變數,被賦值為「沒有值」,而 undefined 比較像是已經建立變數,但未賦值。
文謅謅吧!?我們來跑跑看!
let x = null; // 已定義變數,有賦值,但為空值
let y; // 已定義變數,尚未賦值
console.log(x); // null(空值)
console.log(y); // undefined(未定義值)
相信讀者對這兩個型別有更加的認識了,那... any呢?如果你無法透過官方的型別來定義他,無非就是兩種方法:
看不懂?直接上 code!
const readPhoto = (url: string): any => { // 定義讀取照片函數,傳入圖片網址(型別為 string 字串),傳出每一個像素(型別不知道,因此使用 any)。
const pixel = {}; // 宣告 pixel 像素
// ...(省略)
return pixel;
}
readPhoto('圖片網址');
以這個例子來說,除非開發者自行定義 Photo Pixel 的型別,否則使用 any。但這個做法會造成,他可以傳出 number、string、boolean、undefined、null 等其他型別,會造成開發上的混亂。因此我們能夠盡量定義型別就定義,非必要盡量不使用 any。
那該怎麼自己定義型別呢?我們可以利用保留字 type
,直接上 code:
type userID = number | string; // 定義使用者ID型別可以接受「數字、字串」
const getUID = (uid: userID): string => { // 定義取得UID的函式,接受傳入 uid(型別為 userID),傳出字串
return "U-" + uid // 將 U-{UID} 傳出
}
console.log(getUID("abc123")); // 傳入 abc123(string),傳出 U-abc123(string)。
console.log(getUID(456)); // 傳入 456(number),傳出 U-456(string)。
那如果開發者有很多個 type 呢?那就要使用 interface 了,可以表示一個「物件結構」。
type name = string; // 定義 name 可接受 字串
type uid = number | string; // 定義 uid 可接受 數字或字串
interface userProps { // 定義使用者的參數
name: name; // 使用者可以有 姓名,其型別為 name
uid: uid; // 使用者可以有 uid,其型別為 uid
}
const getUID = (props: userProps): string => { // 定義取得UID的函式,接受傳入參數,傳出字串
return "U-" + props.uid // 將 U-{UID} 傳出
}
const getName = (props: userProps): string => { // 定義取得姓名的函式,接受傳入參數,傳出字串
return props.name // 將 姓名 傳出
}
console.log(getUID({name: "Andy", uid: "abc123"})); // 傳入 {姓名, uid}(此為 Object(物件)),傳出 U-uid(string)
console.log(getUID({name: "Lily", uid: 1000})); // 傳入 {姓名, uid}(此為 Object(物件)),傳出 U-uid(string)
console.log(getName({name: "John", uid: 1001})); // 傳入 {姓名, uid}(此為 Object(物件)),傳出 name(string)
注意一下,Object 也是型別的一種,很類似 JSON,但並不是。
至於 Props 是什麼?在第五篇會介紹到,本篇先不介紹,怕混亂,讀者先知道這個就行。
如果讀者讀不懂,可以多讀幾次,大概就可以看出差別了。
type 比較講的是「單一變數」的型別,而 interface 比較講的是「多個變數」的型別。例如開發者可以定義「使用者」,使用者有姓名和UID。姓名的型別是字串、UID的型別可以是字串和數字,而加工處理後的UID是字串。
如果是 JS 的話,JS就會稍微複雜了。JS 它屬於弱型別,支持動態轉換,幾乎都是直接寫,如果沒有特別檢查的話,很常會發生 Error 或是 Bug。因此透過「強型別定義」,我們可以更好的開發。就以剛剛的例子舉例:
let value1 = 100; // 沒有強加型別
let value2 = "hello"; // 沒有強加型別
console.log(value1 + value2); // 100hello
讀者可能會很納悶,為什麼 100 可以跟字串加在一起?別懷疑XD,就是可以。至於要探討為什麼可以,其實就是「弱型別」所造成的,其實弱型別針對瀏覽器來說是更好的,因為不會網頁跑一跑就不能跑,更強調的是「可適配性」,以瀏覽器能夠兼容為主。而強型別是針對「開發階段」。那是不是就可以理解為什麼瀏覽器傾向吃JS,而開發傾向吃TS,正是因為雙方要的不同,使用者要的是方便,開發者要的是嚴謹。至於還有許多原因,歡迎自行查詢相關資料,本文就不再贅述。
相信讀者從上面的範例來看,對 TS 已經有一個基礎的認識,正所謂「師傅引進門,修行在個人」,此教學重不在「複雜的專業」,而是在「讓你跨出第一步」。
讀者可以從 TS 的結構與語法來看,都可以看出 TS 是強型別,更容易去讀。再加上 TS 本身就已經有規範了,因此 code 並不會差到哪去。當然,一個差的開發者,不管用再規範的語言或是 formatter(語法格式化工具),都可以寫出「屎山代碼」XD。因此我們如果更需要去加強 coding style(程式碼風格),各位可以去讀 clean code(非業配) 或 相關探討程式碼風格和優化(optimize)的書籍或文章,本教學不會講到這一塊。
究竟強型別和弱型別差在哪?我在這邊再舉例一個可讀性的例子:
type 性別型別 = string
interface 八字 {
性別: 性別型別
年: number;
月: number;
日: number;
時: number;
分: number;
}
const 計算八字 = (props: 八字): string => {
let 輕重;
// ...省略(我就不寫了XD,因為我也不知道怎麼計算八字)
return 輕重;
}
console.log(計算八字({
性別: '男',
年: 2000,
月: 1,
日: 1,
時: 08,
分: 0
})) // 傳入函數,輸出告訴你輕還是重
如果是 JavaScript 的話,則是:
const 計算八字 = (性別, 年, 月, 日, 時, 分) => {
let 輕重;
// ...省略(我就不寫了XD,因為我也不知道怎麼計算八字)
return 輕重;
}
console.log(計算八字({
性別: '男',
年: 2000,
月: 1,
日: 1,
時: 08,
分: 0
})) // 傳入函數,輸出告訴你輕還是重
我們來思考一下如果用 JS 會發生什麼是?我性別貌似可以傳入數字,我年月日時分可以傳入字串,那在不判斷的情況下,是不是就非常容易造成錯誤或是 bug?且對可讀性也非常的差,因為開發者可以在 TS 上非常簡便的看出我該傳入什麼傳出什麼,對於多人開發來說,是再適合不過了。另外我們在大型專案多人開發,也不僅會使用 TS,更還會透過「單元測試」來測試數據是否正確或極端案例(edge cases)是否可以處理得當。但本教學不會特別說明更進階的單元測試,如果有興趣可以自己查資料學習。
這裡再補一句,其實 JS 算是動態型別,可以幫忙轉型,但這種方便相對的就會變成隱患。但這裡為了好理解,就稱其為弱型別吧。
說完了一堆優點,我們來看看缺點吧!其實不難理解,他的優點就是他的缺點,他高度可讀性,相對的就是需要寫更多「規範」,開發者需要規範型別。同樣的邏輯,在 JavaScript 開發者可以更少的 code 實現,但得到的就是更多的「不確定性及風險」,當然拉,你也可以全部都用 any 混過去XD,那你就沒有使用 TS 的必要了吧?但以長期來說,仍然會是強型別更有辦法維護。我也建議新手一開始就學習 TS,建立良好的觀念,等熟了再來寫 JS 吧,會避掉非常多坑洞的。
我這邊呢,就分三個來講:
本篇還有非常多很有趣的 TS 保留字和結構沒講,例如泛型、繼承、斷言,以及非常多的型別沒提到。因為篇幅的關係,我就不再這裡一一贅述了,主要只是想讓讀者學習 TS,接下來的教學呢,為了讓新手更好理解,以及奠定更扎實的基礎,都會以 TS 為主。
我們可以看到使用 TS 益處多多,雖然他也有許多缺點,例如轉譯速度等,但我覺得再說下去,應該要講好久了,以後如果有機會可以再說到這些,我們就邊做邊學吧~
好了,本篇就到此為止,下一章我們終於可以來講講 Vue.JS 了,花了一整篇講 TS,算是先幫讀者惡補一下哈哈。下一章我們來介紹,本教學的第一個階段,《一起來安裝 Vue.JS 及 Vite 吧! - Day 3》。